using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Text;
using System.Xml;
using APH.Parl2.Business.Publishing.DocBook;

namespace APH.Parl2.Business.Publishing
{
    class WordXmlToDocBookConverter : IDocBookConverter
    {
        private static readonly Uri DocumentUri = new Uri("/word/document.xml", UriKind.Relative);
        private static readonly Uri CorePropertiesUri = new Uri("/docProps/core.xml", UriKind.Relative);

        private readonly Stream _wordDocument;
        private Package _package;
        private Article _article;

        public WordXmlToDocBookConverter(Stream wordDocument)
        {
            _wordDocument = wordDocument;
        }

        public Article Convert()
        {
            Initialize();
            ParseMetadata();
            ParseContent();
            return _article;
        }

        private void ParseContent()
        {
            var documentPart = _package.GetPart(DocumentUri);
            var stream = documentPart.GetStream(FileMode.Open, FileAccess.Read);
            var xmlReader = XmlReader.Create(stream);
            xmlReader.ReadToDescendant("w:body");
            var innerReader = xmlReader.ReadSubtree();
            foreach (var section in ParseSection(innerReader))
            {
                _article.Sections.Add(section);
            }
        }

        private static IEnumerable<Section> ParseSection(XmlReader xmlReader)
        {
            var section = new Section();
            bool hasSection = false;
            string style;
            string text;
            while (TryParseParagraph(xmlReader, out style, out text))
            {
                if (style.StartsWith("Heading"))
                {
                    if (hasSection)
                    {
                        yield return section;
                        section = new Section();
                    }
                    section.Title = text;
                }
                else
                {
                    section.Paras.Add(text);
                }
                hasSection = true;
            }
            if (hasSection)
                yield return section;
        }

        private static bool TryParseParagraph(XmlReader xmlReader, out string style, out string text)
        {
            style = "";
            text = "";
            var sb = new StringBuilder();

            if (!xmlReader.ReadToFollowing("w:p"))
                return false;
            using (var innerReader = xmlReader.ReadSubtree())
            {
                while (innerReader.Read())
                {
                    if (innerReader.Name == "w:pPr")
                    {
                        if (innerReader.ReadToDescendant("w:pStyle"))
                        {
                            style = innerReader.GetAttribute("w:val");
                            innerReader.Read();
                        }
                    }
                    if (innerReader.Name == "w:r")
                    {
                        using (var runReader = innerReader.ReadSubtree())
                        {
                            ParseRun(runReader, sb);
                            runReader.Close();
                        }
                    }
                }
                innerReader.Close();
            }
            text = sb.ToString();
            return true;
        }

        private static void ParseRun(XmlReader reader, StringBuilder sb)
        {
            bool isBold = false;
            bool isItalic = false;
            while (reader.Read())
            {
                if (reader.Name == "w:rPr")
                {
                    using (var runPropertiesReader = reader.ReadSubtree())
                    {
                        ParseRunProperties(runPropertiesReader, out isBold, out isItalic);
                        runPropertiesReader.Close();
                    }
                    // TODO these get escaped during the serialization to &lt; etc., need to make this work to cause bold / italicised text
                    if (isBold)
                        sb.Append("<emphasis role=\"bold\">");
                    else if (isItalic)
                        sb.Append("<emphasis>");
                }
                else if (reader.Name == "w:t")
                {
                    sb.Append(reader.ReadString());
                    reader.Read();
                }
            }
            if (isBold)
                sb.Append("</emphasis>");
            if (isItalic)
                sb.Append("</emphasis>");
        }

        private static void ParseRunProperties(XmlReader runPropertiesReader, out bool isBold, out bool isItalic)
        {
            isBold = false;
            isItalic = false;
            while (runPropertiesReader.Read())
            {
                if (runPropertiesReader.Name == "w:b") isBold = true;
                if (runPropertiesReader.Name == "w:i") isItalic = true;
            }
        }

        private void ParseMetadata()
        {
            var corePropertiesPart = _package.GetPart(CorePropertiesUri);
            var stream = corePropertiesPart.GetStream(FileMode.Open, FileAccess.Read);
            var xmlReader = XmlReader.Create(stream);

            if (!xmlReader.ReadToDescendant("cp:coreProperties"))
                throw new FileFormatException("Could not find coreProperties element");

            var info = new Info();
            while (xmlReader.Read())
            {
                if (!xmlReader.IsStartElement()) continue;
                var key = xmlReader.Name;
                var value = xmlReader.ReadString();
                switch (key)
                {
                    case "dc:title":
                        info.Title = value;
                        break;
                    case "dc:creator":
                        var names = value.Split(' ');
                        info.Author = new Author
                        {
                            PersonName = new PersonName
                            {
                                FirstName = names[0],
                                Surname = names.Length > 1 ? names[1] : ""
                            }
                        };
                        break;
                }
            }
            _article.Info = info;
        }

        private void Initialize()
        {
            _article = new Article();
            _package = Package.Open(_wordDocument, FileMode.Open, FileAccess.Read);
        }
    }
}